package krasa.mavenrun.analyzer;
import com.intellij.ide.CommonActionsManager;
import com.intellij.ide.DefaultTreeExpander;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ex.ApplicationInfoEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.BuildNumber;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.*;
import com.intellij.ui.components.JBList;
import com.intellij.util.text.VersionComparatorUtil;
import krasa.mavenrun.ApplicationComponent;
import krasa.mavenrun.analyzer.action.LeftTreePopupHandler;
import krasa.mavenrun.analyzer.action.RightTreePopupHandler;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.model.MavenArtifact;
import org.jetbrains.idea.maven.model.MavenArtifactNode;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.server.MavenServerManager;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.lang.reflect.Method;
import java.util.*;
import java.util.List;
/**
* @author Vojtech Krasa
*/
public class GuiForm {
private static final Logger LOG = Logger.getInstance("#krasa.mavenrun.analyzer.GuiForm");
public static final String WARNING = "Your settings indicates, that conflicts will not be visible, see IDEA-133331\n"
+ "If your project is Maven2 compatible, you could try one of the following:\n"
+ "-use IJ 2016.1+ and configure it to use external Maven 3.1.1+ (File | Settings | Build, Execution, Deployment | Build Tools | Maven | Maven home directory)\n"
+ "-press Apply Fix button to alter Maven VM options for importer (might cause trouble for IJ 2016.1+)\n"
+ "-turn off File | Settings | Build, Execution, Deployment | Build Tools | Maven | Importing | Use Maven3 to import project setting\n"
;
protected static final Comparator<MavenArtifactNode> BY_ARTICATF_ID = new Comparator<MavenArtifactNode>() {
@Override
public int compare(MavenArtifactNode o1, MavenArtifactNode o2) {
return o1.getArtifact().getArtifactId().compareTo(o2.getArtifact().getArtifactId());
}
};
private final Project project;
private final VirtualFile file;
private MavenProject mavenProject;
private JBList leftPanelList;
private JTree rightTree;
private JPanel rootPanel;
private JRadioButton allDependenciesAsListRadioButton;
private JRadioButton conflictsRadioButton;
private JRadioButton allDependenciesAsTreeRadioButton;
private JLabel noConflictsLabel;
private JScrollPane noConflictsWarningLabelScrollPane;
private JTextPane noConflictsWarningLabel;
private JButton refreshButton;
private JSplitPane splitPane;
private SearchTextField searchField;
private JButton applyMavenVmOptionsFixButton;
private JPanel leftPanelWrapper;
private JTree leftTree;
private JCheckBox showGroupId;
private JPanel buttonsPanel;
protected DefaultListModel listDataModel;
protected Map<String, List<MavenArtifactNode>> allArtifactsMap;
protected final DefaultTreeModel rightTreeModel;
protected final DefaultTreeModel leftTreeModel;
protected final DefaultMutableTreeNode rightTreeRoot;
protected final DefaultMutableTreeNode leftTreeRoot;
protected ListSpeedSearch myListSpeedSearch;
protected List<MavenArtifactNode> dependencyTree;
protected CardLayout leftPanelLayout;
private boolean notificationShown;
public GuiForm(final Project project, VirtualFile file, final MavenProject mavenProject) {
this.project = project;
this.file = file;
this.mavenProject = mavenProject;
final ActionListener radioButtonListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateLeftPanel();
}
};
conflictsRadioButton.addActionListener(radioButtonListener);
allDependenciesAsListRadioButton.addActionListener(radioButtonListener);
allDependenciesAsTreeRadioButton.addActionListener(radioButtonListener);
refreshButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
initializeModel();
rootPanel.requestFocus();
}
});
myListSpeedSearch = new ListSpeedSearch(leftPanelList);
searchField.addDocumentListener(new DocumentAdapter() {
@Override
protected void textChanged(DocumentEvent documentEvent) {
updateLeftPanel();
}
});
try {
Method searchField = this.searchField.getClass().getMethod("getTextEditor");
JTextField invoke = (JTextField) searchField.invoke(this.searchField);
invoke.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
if (GuiForm.this.searchField.getText() != null) {
GuiForm.this.searchField.addCurrentTextToHistory();
}
}
});
} catch (Exception e) {
throw new RuntimeException(e);
}
noConflictsWarningLabel.setBackground(null);
applyMavenVmOptionsFixButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String mavenEmbedderVMOptions = MavenServerManager.getInstance().getMavenEmbedderVMOptions();
int baselineVersion = ApplicationInfoEx.getInstanceEx().getBuild().getBaselineVersion();
if (baselineVersion >= 140) {
mavenEmbedderVMOptions += " -Didea.maven3.use.compat.resolver";
} else {
mavenEmbedderVMOptions += " -Dmaven3.use.compat.resolver";
}
MavenServerManager.getInstance().setMavenEmbedderVMOptions(mavenEmbedderVMOptions);
final MavenProjectsManager projectsManager = MavenProjectsManager.getInstance(project);
projectsManager.forceUpdateAllProjectsOrFindAllAvailablePomFiles();
refreshButton.getActionListeners()[0].actionPerformed(e);
}
});
noConflictsWarningLabel.setText(WARNING);
leftPanelLayout = (CardLayout) leftPanelWrapper.getLayout();
rightTreeRoot = new DefaultMutableTreeNode();
rightTreeModel = new DefaultTreeModel(rightTreeRoot);
rightTree.setModel(rightTreeModel);
rightTree.setRootVisible(false);
rightTree.setShowsRootHandles(true);
rightTree.expandPath(new TreePath(rightTreeRoot.getPath()));
rightTree.setCellRenderer(new TreeRenderer(showGroupId));
rightTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
rightTree.addMouseListener(new RightTreePopupHandler(project, mavenProject, rightTree));
leftTree.addTreeSelectionListener(new LeftTreeSelectionListener());
leftTreeRoot = new DefaultMutableTreeNode();
leftTreeModel = new DefaultTreeModel(leftTreeRoot);
leftTree.setModel(leftTreeModel);
leftTree.setRootVisible(false);
leftTree.setShowsRootHandles(true);
leftTree.expandPath(new TreePath(leftTreeRoot.getPath()));
leftTree.setCellRenderer(new TreeRenderer(showGroupId));
leftTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
leftTree.addMouseListener(new LeftTreePopupHandler(project, mavenProject, leftTree));
showGroupId.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
leftPanelList.repaint();
TreeUtils.nodesChanged(GuiForm.this.rightTreeModel);
TreeUtils.nodesChanged(GuiForm.this.leftTreeModel);
}
});
final DefaultTreeExpander treeExpander = new DefaultTreeExpander(leftTree);
DefaultActionGroup actionGroup = new DefaultActionGroup();
actionGroup.add(CommonActionsManager.getInstance().createExpandAllAction(treeExpander, leftTree));
actionGroup.add(CommonActionsManager.getInstance().createCollapseAllAction(treeExpander, leftTree));
ActionToolbar actionToolbar = ActionManagerEx.getInstance().createActionToolbar("krasa.MavenHelper.buttons",
actionGroup, true);
buttonsPanel.add(actionToolbar.getComponent(), "1");
}
private void createUIComponents() {
listDataModel = new DefaultListModel();
leftPanelList = new JBList(listDataModel);
leftPanelList.addListSelectionListener(new MyListSelectionListener());
// no generics in IJ12
leftPanelList.setCellRenderer(new ColoredListCellRenderer() {
@Override
protected void customizeCellRenderer(JList jList, Object o, int i, boolean b, boolean b2) {
MyListNode value = (MyListNode) o;
String maxVersion = value.getMaxVersion();
final String[] split = value.key.split(":");
if (showGroupId.isSelected()) {
append(split[0] + " : ", SimpleTextAttributes.REGULAR_ATTRIBUTES);
}
append(split[1], SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES);
append(" : " + maxVersion, SimpleTextAttributes.REGULAR_ATTRIBUTES);
}
});
rightTree = new MyHighlightingTree();
leftTree = new MyHighlightingTree();
}
public static String sortByVersion(List<MavenArtifactNode> value) {
Collections.sort(value, new Comparator<MavenArtifactNode>() {
@Override
public int compare(MavenArtifactNode o1, MavenArtifactNode o2) {
DefaultArtifactVersion version = new DefaultArtifactVersion(o1.getArtifact().getVersion());
DefaultArtifactVersion version1 = new DefaultArtifactVersion(o2.getArtifact().getVersion());
return version1.compareTo(version);
}
});
return value.get(0).getArtifact().getVersion();
}
private class LeftTreeSelectionListener implements TreeSelectionListener {
@Override
public void valueChanged(TreeSelectionEvent e) {
TreePath selectionPath = e.getPath();
if (selectionPath != null) {
DefaultMutableTreeNode lastPathComponent = (DefaultMutableTreeNode) selectionPath.getLastPathComponent();
MyTreeUserObject userObject = (MyTreeUserObject) lastPathComponent.getUserObject();
final String key = getArtifactKey(userObject.getArtifact());
List<MavenArtifactNode> mavenArtifactNodes = allArtifactsMap.get(key);
if (mavenArtifactNodes != null) {// can be null while refreshing
fillRightTree(mavenArtifactNodes, sortByVersion(mavenArtifactNodes));
}
}
}
}
private class MyListSelectionListener implements ListSelectionListener {
@Override
public void valueChanged(ListSelectionEvent e) {
if (listDataModel.isEmpty() || leftPanelList.getSelectedValue() == null) {
return;
}
final MyListNode myListNode = (MyListNode) leftPanelList.getSelectedValue();
List<MavenArtifactNode> artifacts = myListNode.value;
fillRightTree(artifacts, myListNode.getMaxVersion());
}
}
private void fillRightTree(List<MavenArtifactNode> mavenArtifactNodes, String maxVersion) {
rightTreeRoot.removeAllChildren();
for (MavenArtifactNode mavenArtifactNode : mavenArtifactNodes) {
MyTreeUserObject userObject = MyTreeUserObject.create(mavenArtifactNode, maxVersion);
userObject.showOnlyVersion = true;
final DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(userObject);
fillRightTree(mavenArtifactNode, newNode);
rightTreeRoot.add(newNode);
}
rightTreeModel.nodeStructureChanged(rightTreeRoot);
TreeUtils.expandAll(rightTree);
}
private void fillRightTree(MavenArtifactNode mavenArtifactNode, DefaultMutableTreeNode node) {
final MavenArtifactNode parent = mavenArtifactNode.getParent();
if (parent == null) {
return;
}
final DefaultMutableTreeNode parentDependencyNode = new DefaultMutableTreeNode(new MyTreeUserObject(parent));
node.add(parentDependencyNode);
parentDependencyNode.setParent(node);
fillRightTree(parent, parentDependencyNode);
}
private void initializeModel() {
final Object selectedValue = leftPanelList.getSelectedValue();
dependencyTree = mavenProject.getDependencyTree();
allArtifactsMap = createAllArtifactsMap(dependencyTree);
updateLeftPanel();
rightTreeRoot.removeAllChildren();
rightTreeModel.reload();
leftPanelWrapper.revalidate();
if (selectedValue != null) {
leftPanelList.setSelectedValue(selectedValue, true);
}
}
private void updateLeftPanel() {
listDataModel.clear();
leftTreeRoot.removeAllChildren();
final String searchFieldText = searchField.getText();
boolean conflictsWarning = false;
boolean showNoConflictsLabel = false;
if (conflictsRadioButton.isSelected()) {
for (Map.Entry<String, List<MavenArtifactNode>> s : allArtifactsMap.entrySet()) {
final List<MavenArtifactNode> nodes = s.getValue();
if (nodes.size() > 1 && hasConflicts(nodes)) {
if (searchFieldText == null || s.getKey().contains(searchFieldText)) {
listDataModel.addElement(new MyListNode(s));
}
}
}
showNoConflictsLabel = listDataModel.isEmpty();
BuildNumber build = ApplicationInfoEx.getInstanceEx().getBuild();
int baselineVersion = build.getBaselineVersion();
MavenServerManager server = MavenServerManager.getInstance();
boolean useMaven2 = server.isUseMaven2();
boolean containsCompatResolver139 = server.getMavenEmbedderVMOptions().contains("-Dmaven3.use.compat.resolver");
boolean containsCompatResolver140 = server.getMavenEmbedderVMOptions().contains("-Didea.maven3.use.compat.resolver");
boolean newIDE = VersionComparatorUtil.compare(build.asStringWithoutProductCode(), "145.258") >= 0;
boolean newMaven = VersionComparatorUtil.compare(server.getCurrentMavenVersion(), "3.1.1") >= 0;
if (showNoConflictsLabel && baselineVersion >= 139) {
boolean containsProperty = (baselineVersion == 139 && containsCompatResolver139)
|| (baselineVersion >= 140 && containsCompatResolver140);
conflictsWarning = !containsProperty && !useMaven2;
if (conflictsWarning && newIDE) {
conflictsWarning = conflictsWarning && !newMaven;
}
}
if (!conflictsWarning && newIDE && newMaven && containsCompatResolver140) {
if (!notificationShown) {
notificationShown = true;
final Notification notification = ApplicationComponent.NOTIFICATION.createNotification(
"Fix your Maven VM options for importer", "<html>Your settings causes problems in multi-module Maven projects.<br> " +
" <a href=\"fix\">Remove -Didea.maven3.use.compat.resolver</a> ", NotificationType.WARNING, new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent hyperlinkEvent) {
notification.expire();
String mavenEmbedderVMOptions = MavenServerManager.getInstance().getMavenEmbedderVMOptions();
MavenServerManager.getInstance().setMavenEmbedderVMOptions(mavenEmbedderVMOptions.replace("-Didea.maven3.use.compat.resolver", ""));
final MavenProjectsManager projectsManager = MavenProjectsManager.getInstance(project);
projectsManager.forceUpdateAllProjectsOrFindAllAvailablePomFiles();
}
});
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
Notifications.Bus.notify(notification, project);
}
});
}
}
leftPanelLayout.show(leftPanelWrapper, "list");
} else if (allDependenciesAsListRadioButton.isSelected()) {
for (Map.Entry<String, List<MavenArtifactNode>> s : allArtifactsMap.entrySet()) {
if (searchFieldText == null || s.getKey().contains(searchFieldText)) {
listDataModel.addElement(new MyListNode(s));
}
}
showNoConflictsLabel = false;
leftPanelLayout.show(leftPanelWrapper, "list");
} else { // tree
fillLeftTree(leftTreeRoot, dependencyTree, searchFieldText);
leftTreeModel.nodeStructureChanged(leftTreeRoot);
TreeUtils.expandAll(leftTree);
showNoConflictsLabel = false;
leftPanelLayout.show(leftPanelWrapper, "allAsTree");
}
if (conflictsWarning) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
noConflictsWarningLabelScrollPane.getVerticalScrollBar().setValue(0);
}
});
leftPanelLayout.show(leftPanelWrapper, "noConflictsWarningLabel");
}
buttonsPanel.setVisible(allDependenciesAsTreeRadioButton.isSelected());
noConflictsWarningLabelScrollPane.setVisible(conflictsWarning);
applyMavenVmOptionsFixButton.setVisible(conflictsWarning);
noConflictsLabel.setVisible(showNoConflictsLabel);
}
private boolean fillLeftTree(DefaultMutableTreeNode parent, List<MavenArtifactNode> dependencyTree,
String searchFieldText) {
boolean search = StringUtils.isNotBlank(searchFieldText);
Collections.sort(dependencyTree, BY_ARTICATF_ID);
boolean containsFilteredItem = false;
for (MavenArtifactNode mavenArtifactNode : dependencyTree) {
SimpleTextAttributes attributes = SimpleTextAttributes.REGULAR_ATTRIBUTES;
MyTreeUserObject treeUserObject = new MyTreeUserObject(mavenArtifactNode, attributes);
if (search && contains(searchFieldText, mavenArtifactNode)) {
containsFilteredItem = true;
treeUserObject.highlight = true;
}
final DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(treeUserObject);
containsFilteredItem |= fillLeftTree(newNode, mavenArtifactNode.getDependencies(), searchFieldText);
if (parent == leftTreeRoot) {
if (search && !containsFilteredItem) {
// do not add
} else {
parent.add(newNode);
}
containsFilteredItem = false;
} else {
parent.add(newNode);
}
}
return containsFilteredItem;
}
private boolean contains(String searchFieldText, MavenArtifactNode mavenArtifactNode) {
MavenArtifact artifact = mavenArtifactNode.getArtifact();
String displayStringSimple = artifact.getDisplayStringSimple();
return displayStringSimple.contains(searchFieldText);
}
private boolean hasConflicts(List<MavenArtifactNode> nodes) {
String version = null;
for (MavenArtifactNode node : nodes) {
if (version != null && !version.equals(node.getArtifact().getVersion())) {
return true;
}
version = node.getArtifact().getVersion();
}
return false;
}
private Map<String, List<MavenArtifactNode>> createAllArtifactsMap(List<MavenArtifactNode> dependencyTree) {
final Map<String, List<MavenArtifactNode>> map = new TreeMap<String, List<MavenArtifactNode>>();
addAll(map, dependencyTree, 0);
return map;
}
private void addAll(Map<String, List<MavenArtifactNode>> map, List<MavenArtifactNode> artifactNodes, int i) {
if (map == null) {
return;
}
if (i > 100) {
final StringBuilder stringBuilder = new StringBuilder();
for (MavenArtifactNode s : artifactNodes) {
final String s1 = s.getArtifact().toString();
stringBuilder.append(s1);
stringBuilder.append(" ");
}
LOG.error("Recursion aborted, artifactNodes = [" + stringBuilder + "]");
return;
}
for (MavenArtifactNode mavenArtifactNode : artifactNodes) {
final MavenArtifact artifact = mavenArtifactNode.getArtifact();
final String key = getArtifactKey(artifact);
final List<MavenArtifactNode> mavenArtifactNodes = map.get(key);
if (mavenArtifactNodes == null) {
final ArrayList<MavenArtifactNode> value = new ArrayList<MavenArtifactNode>(1);
value.add(mavenArtifactNode);
map.put(key, value);
} else {
mavenArtifactNodes.add(mavenArtifactNode);
}
addAll(map, mavenArtifactNode.getDependencies(), i + 1);
}
}
@NotNull
private String getArtifactKey(MavenArtifact artifact) {
return artifact.getGroupId() + " : " + artifact.getArtifactId();
}
public JComponent getRootComponent() {
return rootPanel;
}
public JComponent getPreferredFocusedComponent() {
return rootPanel;
}
public void selectNotify() {
if (dependencyTree == null) {
initializeModel();
splitPane.setDividerLocation(0.5);
}
}
}